from flask import Flask, request, jsonify, send_file, send_from_directory, Response
from waitress import serve
from flask_cors import CORS
import os
import glob
import shutil
import subprocess
import tempfile
from pathlib import Path
import win32net
import win32security
import win32netcon
import ntsecuritycon
import struct
import binascii
import uuid

import win32api
import win32con

import logging
import logging.handlers
import sys
import json
import threading
import socket
import psutil
import time

import shared_folder
import buildmodbus
import config



app = Flask(__name__)
CORS(app)
app.config['JSON_AS_ASCII'] = False

TMP_PATH = os.path.join(config.START_BASE_PATH, "tmp")
os.makedirs(TMP_PATH, exist_ok=True)

merge_status = { }
CODE_ERROR_AUTO = 403
CODE_ERROR_NONE = 0
CODE_ERROR_ALL = 222
CODE_OK = 1


def cleanup_temp_files():
    max_age = 7 * 24 * 3600 # 清理单位1周
    """定期清理临时文件"""
    while True:
        now = time.time()
        try:
            output_dir = Path(TMP_PATH)
            for file in output_dir.glob("merged_video_*"):
                file_mtime = os.path.getmtime(file)
                if now - file_mtime > max_age:
                    file.unlink()
        except Exception as e:
            config.custom_err_log("删除缓存视频文件失败", str(e))
        time.sleep(24*3600)  # 每天检查一次

cleanup_thread = threading.Thread(target=cleanup_temp_files)
cleanup_thread.daemon = True
cleanup_thread.start()



def authMiddleware(contex):
    real_ip = contex.headers.get('X-Real-IP')
    if not real_ip:
        if contex.remote_addr == config.IPADDR:
            return True
        return False    
    if config.IPADDR == real_ip:
        return True
    return False



@app.route('/api/log', methods=['GET'])
def model_log():
    model = request.args.get("model")
    if not model:
        return jsonify({"code":CODE_ERROR_NONE, "msg": "参数缺失 model "})

    _path = request.args.get("path")
    if model == "get":
        if not _path:
            page = int(request.args.get('page', 1))
            page_size = int(request.args.get('pagesize', 100))
            return jsonify({"code": CODE_OK, "msg": list_files_in_segments(config.START_EXE_LOG, page, page_size)})
        log_path = os.path.join(config.START_EXE_LOG, _path)    
        if os.path.exists(log_path):
            return send_file(log_path)

    if model == "del":
        if not authMiddleware(request):
            return jsonify({"code": CODE_ERROR_AUTO, "msg": "权限不足"})
        log_path = os.path.join(config.START_EXE_LOG, _path)
        if os.path.exists(log_path):
            try:
                os.remove(log_path)
            except:
                pass
        return jsonify({"code": CODE_OK, "msg": "删除完成"})
    
    return jsonify({"code": CODE_ERROR_NONE, "msg": "日志不存在"})



@app.route('/api/getdata', methods=['POST'])
def api_getdata():
    # 读取电表接口
    dbid = request.form.get("id")
    
    send_data = buildmodbus.read_db_commd(dbid)
    value = buildmodbus.db_dl_jx(buildmodbus.create_tcp_connection(send_data))
    if value == "":
        return jsonify({"code": CODE_ERROR_NONE , "msg": {"value": "", "id": dbid}})
    return jsonify({"code": CODE_OK, "msg": {"value": "{:.2f}".format(value), "id": dbid}})


@app.route('/api/setip', methods=['POST'])
def api_setip():
    # 修改电表地址接口
    dbid = request.form.get("id")
    newid = request.form.get("newid")
    
    send_data = buildmodbus.set_dbip_commd(dbid, newid)
    value = buildmodbus.create_tcp_connection(send_data)
    if value == "":
        return jsonify({"code": CODE_ERROR_NONE , "msg": {"value": "", "id": dbid}})
    return jsonify({"code": CODE_OK, "msg": {"value": value, "id": dbid}})


@app.route('/api/clear', methods=['POST'])
def api_clear():
    # 电表清零接口
    dbid = request.form.get("id")
    
    send_data = buildmodbus.clear_dbip_commd(dbid)
    value = buildmodbus.create_tcp_connection(send_data)
    if value == "":
        return jsonify({"code": CODE_ERROR_NONE , "msg": {"value": "", "id": dbid}})
    return jsonify({"code": CODE_OK, "msg": {"value": value, "id": dbid}})


@app.route('/api/getlist', methods=['POST'])
def api_getlist():
    # 获取电表列表接口
    maxsum = int(request.form.get("max", "5"))
    if maxsum > 9:
        return jsonify({"code": CODE_ERROR_AUTO , "msg": "大于设定的预值"})
        
    applist = []
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        client_socket.settimeout(1.2)
        client_socket.connect((config.CONFIG.CFG["wlip"], int(config.CONFIG.CFG["wlport"])))
        for v in range(1, maxsum):
            bytes_data = bytes.fromhex(buildmodbus.read_db_commd(f"0{v}").replace(" ", ""))
            client_socket.sendall(bytes_data)
            data = client_socket.recv(9)
            if data:
                applist.append(data.hex()[:2])
    except Exception as e:
        pass
    finally:
        client_socket.close()
    return jsonify({"code": CODE_OK, "list": applist})



@app.route('/api/folder', methods=['GET'])
def model_folder():
    model = request.args.get("model")
    if model == "get":
        return jsonify({"code": CODE_OK, "msg": shared_folder.get_shared_folder()})

    if model == "del":
        if not authMiddleware(request):
            return jsonify({"code": CODE_ERROR_AUTO, "msg": "权限不足"})
        name = request.args.get("name")
        path = request.args.get("path")
        if not name or not path:
            return jsonify({"code": CODE_ERROR_NONE, "msg": "参数缺失 name 或 path "})
        if shared_folder.delete_shared_folder(name) == True:
            try:
                shutil.rmtree(path)
            except:
                pass
            return jsonify({"code": CODE_OK, "msg": "删除完成"})
        return jsonify({"code": CODE_ERROR_ALL, "msg": "删除失败 查询日志失败原因"})  
           
    if model == "add":
        if not authMiddleware(request):
            return jsonify({"code": CODE_ERROR_AUTO, "msg": "权限不足"})

        name = request.args.get("name")
        path = request.args.get("path")
        if not name or not path:
            return jsonify({"code": CODE_ERROR_NONE, "msg": "参数缺失 name 或 path "})

        for val in shared_folder.get_shared_folder():
            if val["netname"] == name or os.path.basename(val["path"]) == path:
                return jsonify({"code": CODE_ERROR_ALL, "msg": "共享名称或目录已存在"})

        passwd = request.args.get("passwd")
        isok = shared_folder.create_shared_folder(path, name, config.CONFIG.CFG["description"], passwd)
        if isok:        
            return jsonify({"code": CODE_OK, "msg": isok})
        return jsonify({"code": CODE_ERROR_ALL, "msg": "创建失败 查询日志失败原因"})

    if model == "clear":
        if not authMiddleware(request):
            return jsonify({"code": CODE_ERROR_AUTO, "msg": "权限不足"})
        return jsonify({"code": CODE_OK, "msg": shared_folder.clear_ClentUser()})

    if model == "auto":
        types = request.args.get("type")
        if not types:
            return jsonify({"code": CODE_ERROR_NONE, "msg": "参数缺失 type "})
        if types == "set":
            if not authMiddleware(request):
                return jsonify({"code": CODE_ERROR_AUTO, "msg": "权限不足"})
            flasg = {"read":"FILE_GENERIC_READ", "write":"FILE_GENERIC_WRITE", "execute":"FILE_GENERIC_EXECUTE", "all":"FILE_ALL_ACCESS"}
            if not request.args.get("read"):
                flasg.pop("read")
            if not request.args.get("write"):
                flasg.pop("write")
            if not request.args.get("execute"):
                flasg.pop("execute")
            if not request.args.get("all"):
                flasg.pop("all")
            abs_path = os.path.abspath(request.args.get("path"))
            if os.path.exists(abs_path):
                return jsonify({"code": CODE_OK, "msg": shared_folder.setauto_shared_folder(abs_path, [flasg[key] for key in flasg])})
            return jsonify({"code": CODE_ERROR_NONE, "msg": "路径不存在 " + abs_path})
        if types == "get":
            autos = shared_folder.getauto_shared_folder(request.args.get("path"))
            if autos:
                return jsonify({"code": CODE_OK, "msg": autos})
            return jsonify({"code": CODE_ERROR_ALL, "msg": "权限读取失败 查询日志失败原因"})

    return jsonify({"code": CODE_ERROR_ALL, "msg": "参数错误 model "})




@app.route('/api/regkey', methods=['GET'])
def model_regkey():
    model = request.args.get("model")
    if model == "get":
        regkeyall = shared_folder.getallregkey()
        if regkeyall:
            return jsonify({"code": CODE_OK, "msg": regkeyall})
        return jsonify({"code": CODE_ERROR_ALL, "msg": "查询失败 查询日志失败原因"})

    if model == "set":
        if not authMiddleware(request):
            return jsonify({"code": CODE_ERROR_AUTO, "msg": "权限不足"})

        value = request.args.get("value")
        name = request.args.get("name")
        if not name or not value:
            return jsonify({"code": CODE_ERROR_NONE, "msg": "参数缺失 value 或 name "})

        if hasattr(shared_folder, "set" + name):
            return jsonify({"code": CODE_OK, "msg": getattr(shared_folder, "set" + name)(int(value))})

        return jsonify({"code": CODE_ERROR_NONE, "msg": "没有这个函数 " + name})

    return jsonify({"code": CODE_ERROR_ALL, "msg": "参数错误 model "})



@app.route('/api/search', methods=['POST'])
def model_search(): # 仿小米摄像头通过日期搜索录像
    file_path = request.form.get("path") # 这个参数是小米设备完整目录，由前端构建传参
    value = request.form.get("search")
    search_pattern = os.path.join(file_path, value + "*")
    return jsonify({"code": CODE_OK, "msg": glob.glob(search_pattern)})



@app.route('/api/xiaomi_videos', methods=['GET'])
def xiaomi_videos():
    """使用FFmpeg合并视频并返回"""
    path = request.args.get("path")
    model = request.args.get("model")
    if not path:
        return "缺少路径参数", 400
    
    # 安全检查：确保路径是合法目录
    if not os.path.isdir(path):
        return "路径不存在或不是目录", 400
    
    taskid = os.path.basename(path)
    if model == "check":
        if taskid in merge_status:
            return jsonify({"code": CODE_OK, "msg": "任务转换中"})

        FileListPath = os.path.join(TMP_PATH, f"merged_video_{taskid}.txt")
        if os.path.exists(FileListPath):
            with open(FileListPath, "r", encoding="utf-8") as f:
                clen = f.readlines()
                if len(clen) < len(os.listdir(path)):
                    merge_status[taskid] = "processing" # 这里标记任务进行中
                    task_thread = threading.Thread(target=ffmpeg_video_to_mp4, args=(path, taskid))
                    task_thread.daemon = True
                    task_thread.start()
                    return jsonify({"code": CODE_OK, "msg": "开始任务转换"})
                return jsonify({"code": CODE_OK, "msg": "合成文件存在"})

        merge_status[taskid] = "processing" # 这里标记任务进行中
        task_thread = threading.Thread(target=ffmpeg_video_to_mp4, args=(path, taskid))
        task_thread.daemon = True
        task_thread.start()
        return jsonify({"code": CODE_OK, "msg": "开始任务转换"})

    output_path = os.path.join(TMP_PATH, f"merged_video_{taskid}.mp4")
    if not os.path.exists(output_path):
        return "未找到MP4文件", 404
    return send_cached_file(output_path)



@app.route('/api/videos', methods=['GET'])
def model_videos():
    file_path = request.args.get("path")
    page = int(request.args.get('page', 1))
    page_size = int(request.args.get('pagesize', 100))
    resdic = {"code": CODE_ERROR_ALL}

    if os.path.exists(file_path):
        if os.path.isfile(file_path):
            return send_file(file_path)

        elif os.path.isdir(file_path):
            list_dir = list_files_in_segments(file_path, page, page_size)
            ret_list = [(_path, os.path.isdir(os.path.join(file_path, _path))) for _path in list_dir]
            resdic.update({"code": CODE_OK, "msg": ret_list})
            return jsonify(resdic)

    resdic.update({"msg": "路径不存在"})
    return jsonify(resdic)



@app.route('/api/delfile', methods=['GET'])
def model_delfile():
    path = request.args.get("path")
    if not authMiddleware(request):
        return jsonify({"code": CODE_ERROR_AUTO, "msg": "权限不足"})  

    if not path:
        return jsonify({"code": CODE_ERROR_NONE, "msg": "参数缺失 path "})

    try:
        if os.path.isfile(path):
            os.remove(path)
        elif os.path.isdir(path):
            shutil.rmtree(path)
        return jsonify({"code": CODE_OK, "msg": "删除完成"})

    except Exception as e:
        return jsonify({"code": CODE_ERROR_ALL, "msg": e})



@app.route('/api/sysconfig', methods=['GET'])
def sys_config():
    if not authMiddleware(request):
        return jsonify({"code": CODE_ERROR_AUTO, "msg": "权限不足"})  
    model = request.args.get("model")
    if not model:
        return jsonify({"code": CODE_ERROR_NONE, "msg": "参数缺失 model "})

    if model == "get":
        return jsonify({"code": CODE_OK, "msg": {
            "ipaddr": config.IPADDR,
            "main_path": os.path.dirname(config.START_EXE_PATH),
            "log_path": config.START_EXE_LOG,
            "description": config.CONFIG.CFG.get("description"),
            "folder_path": config.CONFIG.CFG.get("folder_path"),
            "lan_name": config.CONFIG.CFG.get("lan_name"),
            "port": config.CONFIG.CFG.get("port"),
            "static_path": config.CONFIG.CFG.get("static_path"),
            "wlip": config.CONFIG.CFG.get("wlip"),
            "wlport": config.CONFIG.CFG.get("wlport"),
        }})

    elif model == "set":
        key = request.args.get("key")
        value = request.args.get("value")
        if config.CONFIG.CFG.get(key):
            if key in ["folder_path", "static_path"]:
                mpath = os.path.abspath(value)
                if not os.path.isdir(mpath):
                    return jsonify({"code": CODE_ERROR_NONE, "msg": "目录不存在"})

                config.CONFIG.save_json({"key": key, "value": mpath})
            elif key == "port":
                try:
                    config.CONFIG.save_json({"key": key, "value": int(value)})
                except ValueError:
                    return jsonify({"code": CODE_ERROR_ALL, "msg": "参数类型错误"})

            else:
                config.CONFIG.save_json({"key": key, "value": value})
            return jsonify({"code": CODE_OK, "msg": "更新成功"})

        return jsonify({"code": CODE_ERROR_NONE, "msg": "修改键不存在"})

    return jsonify({"code": CODE_ERROR_ALL, "msg": "参数错误 model "})



@app.route('/api/auth', methods=['GET'])
def sys_auth():
    return jsonify({"auth": authMiddleware(request)})



@app.route('/api/app_restart', methods=['GET'])
def sys_resrart():
    if not authMiddleware(request):
        return jsonify({"code": CODE_ERROR_AUTO, "msg": "权限不足"})
    model = request.args.get("model")
    if model == "1":
        return os._exit(0)
    elif model == "2":
        return os.execl(sys.executable, sys.executable)

    return jsonify({"code": CODE_ERROR_ALL, "msg": "参数错误 model "})



@app.route('/<path:filename>')
def serve_static(filename):
    if "/" not in filename:
        filename += ".html"
    return send_from_directory(config.CONFIG.CFG["static_path"], filename)



@app.route('/')
def index_main():
    return send_from_directory(config.CONFIG.CFG["static_path"], "index.html")



def list_files_in_segments(directory, page, page_size):
    start_index = (page - 1) * page_size
    end_index = start_index + page_size
    entries = list(os.scandir(directory))
    entries.sort(key=lambda entry: entry.stat().st_ctime)
    return [entry.name for entry in entries][start_index : end_index]


def send_cached_file(file_path):
    """发送已缓存的视频文件"""
    cache_duration = 86400 * 7  # 7天缓存
    response = send_file(
        file_path,
        as_attachment=False,
        download_name="merged_video.mp4",
        mimetype='video/mp4',
        conditional=True,
        etag=True
    )
    response.cache_control.max_age = cache_duration
    response.cache_control.public = True
    response.expires = int(time.time() + cache_duration)
    return response



def ffmpeg_video_to_mp4(path, taskid):
    ffmpeg = "ffmpeg.exe"
    try:
        output_path = os.path.join(TMP_PATH, f"merged_video_{taskid}.mp4")
        video_files = glob.glob(os.path.join(path, "*.mp4"))
        video_files = sorted(video_files)  # 按文件名排序
        # 创建文件列表
        file_list_path = os.path.join(tempfile.gettempdir(), f"filelist_{uuid.uuid4().hex}.txt")
        with open(file_list_path, 'w') as f:
            for video_file in video_files:
                f.write(f"file '{os.path.abspath(video_file)}'\n")
        # 使用FFmpeg合并视频
        cmd = [
            ffmpeg, 
            '-y',
            '-f', 'concat', 
            '-safe', '0', 
            '-i', file_list_path,
            '-c:v', 'copy',      # 视频流直接复制
            '-c:a', 'aac',       # 音频流转换为AAC编码
            '-b:a', '128k',      # 设置音频比特率
            output_path
        ]
        result = subprocess.run(cmd, capture_output=True, timeout=300)
        os.remove(file_list_path)
        if result.returncode == 0:
            with open(os.path.join(TMP_PATH, f"merged_video_{taskid}.txt"), "w", encoding="utf-8") as f:
                f.write("\n".join(video_files))
        else:
            config.custom_err_log("视频合成转换错误", result)
    except subprocess.TimeoutExpired:
        config.custom_err_log("视频合成转换超时", taskid)
    except Exception as e:
        config.custom_err_log("视频合成转换错误", taskid, str(e))
    del merge_status[taskid]



if __name__ == "__main__":
    app.run(host='0.0.0.0', port=config.CONFIG.CFG["port"])
    #serve(app, host='0.0.0.0', port=config.CONFIG.CFG["port"])